package com.ruoyi.file.service.impl;

import cn.hutool.core.util.StrUtil;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import com.ruoyi.file.config.HuaWeiObsConfig;
import com.ruoyi.file.domain.MergeFileParams;
import com.ruoyi.file.service.IHuaWeiObsService;
import com.ruoyi.file.utils.FileUploadUtils;
import com.ruoyi.system.api.params.UploadChunkParams;
import com.ruoyi.system.api.params.UploadFileParams;
import com.ruoyi.system.api.vo.UploadFileVO;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.rmi.ServerException;
import java.util.*;

/**
 * 华为云对象存储服务
 *
 * @author ZhangYu
 */
@Slf4j
@Service
public class HuaWeiObsServiceImpl implements IHuaWeiObsService {

    @Resource
    private HuaWeiObsConfig huaWeiObsConfig;

    @Override
    public String uploadFile(@NotNull UploadFileParams uploadFileParams) throws IOException {
        MultipartFile file = uploadFileParams.getFile();
        //获取华为云Obs参数
        String endPoint = huaWeiObsConfig.getEndPoint();
        String accessKeyId = huaWeiObsConfig.getAccessKeyId();
        String accessKeySecret = huaWeiObsConfig.getSecretAccessKey();
        String bucketName = huaWeiObsConfig.getBucketName();
        //创建文件名
        String originalFilename = file.getOriginalFilename();
        assert originalFilename != null;
        String fileName = uploadFileParams.getBiz() + FileUploadUtils.productionFolder() + UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf(StrUtil.DOT));
        //获取上传的文件的输入流
        InputStream inputStream = file.getInputStream();
        try {
            //上传文件到OBS
            ObsClient obsClient = new ObsClient(accessKeyId, accessKeySecret, endPoint);
            obsClient.putObject(bucketName, fileName, inputStream);
            //关闭obsClient
            obsClient.close();
        } catch (ObsException e) {
            log.info("------------------------------------------------------------------------------------------请求失败");
            log.info("---------------------------------------------------------------http状态码：{}", e.getResponseCode());
            log.info("-----------------------------------------------------------------服务端错误码：{}", e.getErrorCode());
            log.info("--------------------------------------------------------------详细错误信息：{}", e.getErrorMessage());
            log.info("----------------------------------------------------------------请求id：{}", e.getErrorRequestId());
            log.info("-------------------------------------------------------------------服务id：{}", e.getErrorHostId());
            throw new ServerException("文件上传失败");
        } catch (Exception e) {
            log.error("-----------------------------------------------------------------------------------------请求失败");
            throw new ServerException("文件上传失败");
        }
        //把上传到oss的路径返回
        return endPoint.split(FileUploadUtils.DOUBLE_SLASH)[0] + FileUploadUtils.DOUBLE_SLASH + bucketName + StrUtil.DOT + endPoint.split(FileUploadUtils.DOUBLE_SLASH)[1] + StrUtil.SLASH + fileName;
    }

    @Override
    public String huaweiShardingUpload(@NotNull UploadFileParams uploadFileParams) throws IOException {
        MultipartFile multipartFile = uploadFileParams.getFile();
        String ak = huaWeiObsConfig.getAccessKeyId();
        String sk = huaWeiObsConfig.getSecretAccessKey();
        String endPoint = huaWeiObsConfig.getEndPoint();
        String bucketName = huaWeiObsConfig.getBucketName();
        //创建文件名
        String originalFilename = multipartFile.getOriginalFilename();
        assert originalFilename != null;
        String fileName = uploadFileParams.getBiz() + FileUploadUtils.productionFolder() + UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf(StrUtil.DOT));
        ObsClient obsClient = new ObsClient(ak, sk, endPoint);
        try {
            //每段上传100MB
            long partSize = 100 * 1024 * 1024L;
            File file = FileUploadUtils.transferToFile(multipartFile);
            long fileSize = file.length();
            //计算需要上传的段数
            long partCount = fileSize % partSize == 0 ? fileSize / partSize : fileSize / partSize + 1;
            final List<PartEtag> partEtagList = new ArrayList<>();
            //初始化分段上传任务
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileName);
            InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);
            final String uploadId = result.getUploadId();
            //上传段
            for (int i = 0; i < partCount; i++) {
                //分段在文件中的起始位置
                final long offset = i * partSize;
                //分段大小
                final long currPartSize = (i + 1 == partCount) ? fileSize - offset : partSize;
                //分段号
                final int partNumber = i + 1;
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setObjectKey(fileName);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setFile(file);
                uploadPartRequest.setPartSize(currPartSize);
                uploadPartRequest.setOffset(offset);
                uploadPartRequest.setPartNumber(partNumber);
                UploadPartResult uploadPartResult;
                uploadPartResult = obsClient.uploadPart(uploadPartRequest);
                log.info("--------------------------------------------上传第【{}】段分片：{}", partNumber, uploadPartResult);
                partEtagList.add(new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()));
            }
            //合并段
            CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, fileName, uploadId, partEtagList);
            obsClient.completeMultipartUpload(completeMultipartUploadRequest);
            log.info("------------------------------------------------------------------------------------------合并完成");
        } catch (ObsException e) {
            log.info("---------------------------------------------------------------http状态码：{}", e.getResponseCode());
            log.info("-----------------------------------------------------------------服务端错误码：{}", e.getErrorCode());
            log.info("--------------------------------------------------------------详细错误信息：{}", e.getErrorMessage());
            log.info("----------------------------------------------------------------请求id：{}", e.getErrorRequestId());
            log.info("-------------------------------------------------------------------服务id：{}", e.getErrorHostId());
            log.error("-----------------------------------------------------------------------------------obs上传失败", e);
            throw new ServerException("文件上传失败");
        } finally {
            try {
                obsClient.close();
            } catch (ObsException | IOException e) {
                log.error("--------------------------------------------------------------------------关闭OBS客户端失败", e);
            }
        }
        //把上传到oss的路径返回
        return endPoint.split(FileUploadUtils.DOUBLE_SLASH)[0] + FileUploadUtils.DOUBLE_SLASH + bucketName + StrUtil.DOT + endPoint.split(FileUploadUtils.DOUBLE_SLASH)[1] + StrUtil.SLASH + fileName;
    }

    @Override
    public UploadFileVO getUploadId(String biz, @NotNull String originalFilename) throws ServerException {
        String ak = huaWeiObsConfig.getAccessKeyId();
        String sk = huaWeiObsConfig.getSecretAccessKey();
        String endPoint = huaWeiObsConfig.getEndPoint();
        String bucketName = huaWeiObsConfig.getBucketName();
        String fileName = biz + FileUploadUtils.productionFolder() + UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf(StrUtil.DOT));
        ObsClient obsClient = new ObsClient(ak, sk, endPoint);
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileName);
        InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);
        String uploadId = result.getUploadId();
        try {
            obsClient.close();
        } catch (IOException e) {
            throw new ServerException("获取上传组id失败", e);
        }
        return UploadFileVO.builder().uploadId(uploadId).fileName(fileName).build();
    }

    @Override
    public PartEtag uploadChunk(UploadChunkParams uploadChunkParams) throws ServerException {
        String ak = huaWeiObsConfig.getAccessKeyId();
        String sk = huaWeiObsConfig.getSecretAccessKey();
        String endPoint = huaWeiObsConfig.getEndPoint();
        String bucketName = huaWeiObsConfig.getBucketName();
        PartEtag partETag;
        try {
            ObsClient obsClient = new ObsClient(ak, sk, endPoint);
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setUploadId(uploadChunkParams.getUploadId());
            uploadPartRequest.setObjectKey(uploadChunkParams.getFileName());
            uploadPartRequest.setInput(uploadChunkParams.getFile().getInputStream());
            //分段在文件中的起始位置
            uploadPartRequest.setOffset((long) uploadChunkParams.getChunkIndex() * uploadChunkParams.getChunkSize());
            // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
            uploadPartRequest.setPartSize((long) uploadChunkParams.getChunkSize());
            // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
            uploadPartRequest.setPartNumber(uploadChunkParams.getChunkIndex() + 1);
            // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = obsClient.uploadPart(uploadPartRequest);
            partETag = new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber());
            obsClient.close();
        } catch (Exception e) {
            log.error("-------------------------------------------------------------OBS上传文件Chunk：{}", e.getMessage());
            throw new ServerException("OBS合并文件失败", e);
        }
        return partETag;
    }

    @Override
    public String mergeFile(MergeFileParams mergeFileParams) throws ServerException {
        String ak = huaWeiObsConfig.getAccessKeyId();
        String sk = huaWeiObsConfig.getSecretAccessKey();
        String endPoint = huaWeiObsConfig.getEndPoint();
        String bucketName = huaWeiObsConfig.getBucketName();
        try {
            ObsClient obsClient = new ObsClient(ak, sk, endPoint);
            CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(bucketName, mergeFileParams.getFileName(), mergeFileParams.getUploadId(), mergeFileParams.getChunkInfoList());
            obsClient.completeMultipartUpload(request);
            obsClient.close();
        } catch (Exception e) {
            throw new ServerException("OBS合并文件失败", e);
        }
        //把上传到oss的路径返回
        return endPoint.split(FileUploadUtils.DOUBLE_SLASH)[0] + FileUploadUtils.DOUBLE_SLASH + bucketName + StrUtil.DOT + endPoint.split(FileUploadUtils.DOUBLE_SLASH)[1] + StrUtil.SLASH + mergeFileParams.getFileName();
    }

}
